// Extend.js (http://code.google.com/p/extendjs/)
// Copyright 2010 Shumpei Shiraishi All rights reserved.
// Licensed under the MIT license.

(function(_global) {
    // string
    X.extend("String", function(value) {
	return typeof value === "string";
    });
    X.String.extend({
	isString: function() {
	    return true;
	},
	startsWith: function(pattern) {
	    return this.__val.indexOf(pattern) === 0;
	},
	startsWithIgnoreCase: function(pattern) {
	    pattern = String(pattern);
	    var str1 = this.__val.substr(0, pattern.length).toLowerCase();
	    var str2 = pattern.toLowerCase();
	    return str1 === str2;
	},
	endsWith: function endsWith(pattern) {
	    var val = this.__val;
	    var d = val.length - pattern.length;
	    return d >= 0 && val.lastIndexOf(pattern) === d;
	},
	endsWithIgnoreCase: function(pattern) {
	    pattern = String(pattern);
	    var val = this.__val;
	    var d = val.length - pattern.length;
	    if (d >= 0) {
		return val.substr(d) === pattern.toLowerCase();
	    }
	    return false;
	},
	repeat: function(count) {
	    return count < 1 ? '' : new Array(count + 1).join(this.__val);
	},
	truncate: function(length, truncation) {
	    var str = this.__val;
	    length = length || 30;
	    truncation = truncation === undefined ? '...' : truncation;
	    return str.length > length ?
		str.slice(0, length - truncation.length) + truncation : String(str);
	},
	strip: function() {
	    return this.__val.replace(/^\s+/, '').replace(/\s+$/, '');
	},
	stripTags: function() {
	    return this.__val.replace(/<\w+(\s+(\"[^\"]*\"|\'[^\']*\'|[^>])+)?>|<\/\w+>/gi, '');
	},
	escapeHTML: function() {
	    return this.__val.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
	},
	unescapeHTML: function() {
	    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
	},
	camelize: function() {
	    var str = this.__val;
	    var parts = str.split('-'), len = parts.length;
	    if (len == 1) return parts[0];
	    var camelized = str.charAt(0) == '-'
		? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
		: parts[0];
	    for (var i = 1; i < len; i++)
		camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
	    return camelized;
	},
	capitalize: function() {
	    var str = this.__val;
	    return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
	},
	indexOfIgnoreCase: function(str) {
	    return this.__val.toLowerCase().indexOf(str.toLowerCase());
	},
	lastIndexOfIgnoreCase: function(str) {
	    return this.__val.toLowerCase().lastIndexOf(str.toLowerCase());
	},
	isEmpty: function() {
	    return this.__val.length === 0;
	},
	isBlank: function() {
	    return /^[\s\xa0]*$/.test(this.__val);
	},
	isBreakingWhitespace: function() {
	    return !/[^\t\n\r ]/.test(this.__val);
	},
	isAlpha: function() {
	    return !/[^a-zA-Z]/.test(this.__val);
	},
	isNumeric: function() {
	    return !/[^0-9]/.test(this.__val);
	},
	isAlphaNumeric: function() {
	    return !/[^a-zA-Z0-9]/.test(this.__val);
	},
	isSpace: function() {
	    return this.__val === ' ';
	},
	isUnicodeChar: function() {
	    var ch = this.__val;
	    return ch.length == 1 && ch >= ' ' && ch <= '~' ||
		ch >= '\u0080' && ch <= '\uFFFD';
	},
	compareIgnoreCase: function(str2) {
	    var str1 = this.__val;
	    var test1 = String(str1).toLowerCase();
	    var test2 = String(str2).toLowerCase();
	    if (test1 < test2) {
		return -1;
	    } else if (test1 == test2) {
		return 0;
	    } else {
		return 1;
	    }
	},
	trim: function() {
	    // Since IE doesn't include non-breaking-space (0xa0) in their \s character
	    // class (as required by section 7.2 of the ECMAScript spec), we explicitly
	    // include it in the regexp to enforce consistent cross-browser behavior.
	    return this.__val.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '')
	},
	trimLeft: function() {
	    // Since IE doesn't include non-breaking-space (0xa0) in their \s character
	    // class (as required by section 7.2 of the ECMAScript spec), we explicitly
	    // include it in the regexp to enforce consistent cross-browser behavior.
	    return this.__val.replace(/^[\s\xa0]+/, '');
	},
	trimRight: function() {
	    // Since IE doesn't include non-breaking-space (0xa0) in their \s character
	    // class (as required by section 7.2 of the ECMAScript spec), we explicitly
	    // include it in the regexp to enforce consistent cross-browser behavior.
	    return this.__val.replace(/[\s\xa0]+$/, '');
	},
	subs: function(var_args) {
	    var str = this.__val;
	    // This appears to be slow, but testing shows it compares more or less
	    // equivalent to the regex.exec method.
	    for (var i = 0; i < arguments.length; i++) {
		// We cast to String in case an argument is a Function.  Replacing $&, for
		// example, with $$$& stops the replace from subsituting the whole match
		// into the resultant string.  $$$& in the first replace becomes $$& in the
		//  second, which leaves $& in the resultant string.  Also:
		// $$, $`, $', $n $nn
		var replacement = String(arguments[i]).replace(/\$/g, '$$$$');
		str = str.replace(/\%s/, replacement);
	    }
	    return str;
	},
	newLineToBr: function(opt_xml) {
	    return this.__val.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br/>' : '<br>');
	},
	inspect: function(useDoubleQuotes) {
	    var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
		if (character in String.specialChar) {
		    return String.specialChar[character];
		}
		return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
	    });
	    if (useDoubleQuotes) {
		return '"' + escapedString.replace(/\"/g, '\\"') + '"';
	    } else {
		return "'" + escapedString.replace(/\'/g, '\\\'') + "'";
	    }
	},
	padLeft: function(size, ch) {
	    var str = this.__val;
	    var n = size - str.length;
	    for (var i = 0; i < n; i++) {
		str = ch + str;
	    }
	    return str;
	},
	padRight: function(size, ch) {
	    var str = this.__val;
	    var n = size - str.length;
	    for (var i = 0; i < n; i++) {
		str += ch;
	    }
	    return str;
	},
	reverse: function() {
	    var arr = [], str = this.__val;
	    for (var i = str.length; i >= 0; i--) {
		arr.push(str.charAt(i));
	    }
	    return arr.join("");
	},
	left: function(len) {
	    var str = this.__val;
	    len = Math.min(len, str.length);
	    return str.substr(0, len);
	},
	right: function(len) {
	    var str = this.__val;
	    len = Math.min(len, str.length);
	    return str.substr(str.length - len);
	},
	namespace: function() {
	    var current = _global;
	    X(this.val().split(".")).each(function(nm) {
		if (current[nm] === undefined) {
		    current[nm] = {};
		}
		current = current[nm];
	    });
	    return current;
	},
	eachChar: function(f, opt_obj) {
	    var str = this.__val;
	    for (var i = 0, n = str.length; i < n; i++) {
		f.call(opt_obj, str.charAt(i));
	    }
	},
	length: function() {
	    return this.__val.length;
	}
    });
    var NATIVE_METHODS_STRING = [
	"toString", "valueOf", "charAt", "charCodeAt", "concat", "indexOf",
	"lastIndexOf", "localeCompare", "match", "replace",
	"search", "slice", "split", "substring", "toLowerCase",
	"toLocaleLowerCase", "toUpperCase", "toLocaleUpperCase",
	"trim", "toJSON"
    ];
    // overwrite by native implementation
    X.String.mixin(String.prototype, NATIVE_METHODS_STRING);
})(this);
